#ifndef __UDP_SERVER_HPP_
#define __UDP_SERVER_HPP_
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <cstdio>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "log.hpp"
class UdpServer
{
public:
    UdpServer( uint16_t port ,const std::string &ip = "") // 构造函数要接收两个参数，IP+端口号
        : _port(port),_ip(ip)
    {
    }
    ~UdpServer()
    {
        close(_sock);
    }

    // 开始编写我们服务器的两个操作：1.服务器初始化  2.服务器运行
    bool initServer() // 初始化服务器
    {
        // 1.首先第一步，服务器要进行网络通信，因为传输层在操作系统内部我们想要将数据向下传递就必须要有传输层给我们应用层提供的网络系统调用接口
        // 所以隆重介绍：创建套接字的接口------int socket(int domain, int type, int protocol);
        // 那这三个参数是什么呢？
        //   第一个domain表示套接字的域，就是你这个套接字是什么类型的套接字，我们是网络套接字所以填AF_INET
        //   第二个参数type表示是以那种方式进行通信的，我们UDP是基于数据报的，所以填SOCK_DGRAM
        //   第三个参数表示你用的是什么协议，在前两个参数确定了之后也就确定了什么协议，所以不用填
        // 返回值为什么是一个int?
        //   因为socket做了一个类型文件系统的封装，返回了一个文件描述符
        _sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sock < 0)
        {
            logMessage(FATAL, "socket error %s", strerror(errno));
            exit(1);
        }
        // 2.好套接字创建好了，问一个问题我们只有一个套接字我们就可以和客户端主机进行通信了吗？当然不行，要有IP和端口号！我们前面所接收的用户给我们的端口号
        // IP和端口号被使用了吗？没有！所以我正式介绍我们要做的下一步！绑定主机的IP和端口号！
        // 函数原型：int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
        //   第一个参数毫无疑问就是我们创建的套接字，要绑定到哪个套接字的文件描述符上。
        //   第二个参数struct sockaddr* 是不是就是我们操作系统设计人员设计的的一个公共的结构体类型，由于我们是网络通信，所以我们使用struct sockaddr_in这样类型的结构体
        //   第三个参数就是结构体的大小
        //   那struct sockaddr_in这个结构体怎么填充呢？再来看这个结构体的字段。包含头文件：使用man inet查看要包含哪个头文件。
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);// 创建出来结构体后要做清零
        //  这时候我们发现要填充的就是三个字段，分别是sin_family(协议家族)，sin_addr(IP地址)，sin_port(端口号)
        //  要注意的是因为我们要进行的是网络通信，这个结构体要发到网络上，网络序列是大端，所以我们就要把数据从主机序列转化为网络序列
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = _ip.empty()?INADDR_ANY:inet_addr(_ip.c_str()); // 帮我们做了两件事，字符串转化为四字节32位二进制，主机序列转为网络序列
                                                                               // 如果没有传IP那么就
        local.sin_port = htons(_port);                                         // 主机转网络，s表示转成的是16位字节序
        //  到这里我们填充完成，我们就可以开始bind了
        if (bind(_sock, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "bind error %s", strerror(errno));
            exit(2);
        }
        // 这里开始我们的bind就完成了
    }

    // 这里开始就是我们的服务器运行时代码，一个服务器是不是一直都在运行啊？所以服务器是一个死循环->常驻进程->不退出
    void Start()
    {
        // 那服务器要做什么？处理客户端发送的数据啊！那么数据从哪来？从网络中来！所以我们就可以使用系统调用从网络中获取数据

        for (;;)
        {
            // 1.获取数据    介绍接口:  ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
            //   第一个参数就是我们创建的套接字文件描述符
            //   第二个参数和第三个参数就是  你从网络中读取的数据要存储在哪里的缓冲区以及缓冲区的大小，这里要注意数据是从网络中来的，所以要从网络序列转为主机序列
            //   第四个参数设为0就行。
            //   第五个参数存放的就是    给你发送的客户端的IP和PORT也就是    你收到的这个数据是由哪个IP和端口号绑定的唯一的进程发的
            //   第六个参数就是缓冲区大小    注意 5、6参数都是输出型参数，也就是操作系统会把收到的客户端IP和端口存入你传过来的这个结构体指针指向的结构体当中
            //   第六个参数还是一个输入输出参数      输入：struct sockaddr_in结构体大小     输出:   实际操作系统放入数据的结构体大小
            //   所以你要传入你这个struct sockaddr_in的结构体大小
            char buffer[1024];//今天我们就把数据看作是字符串，使用字符串方式处理数据
            struct sockaddr_in result;
            memset(&result, 0, sizeof result);// 创建出来结构体后要做清零
            socklen_t len = sizeof result;
            ssize_t s = recvfrom(_sock,buffer,sizeof(buffer)-1, 0, (struct sockaddr*)&result, &len);//这里缓冲区-1表示未来缓冲区满了，我们在尾部添加/0时不会造成[1024]越界
            if(s != -1)
            {
                buffer[s] = 0; // 因为是字符串我们按照字符串方式处理
                //这个时候我们就有了数据了，不过数据是从网络中来的，所以我们要进行转换
                uint16_t src_port = ntohs(result.sin_port); 
                std::string src_ip = inet_ntoa(result.sin_addr);
                printf("[%s:%d] %s\n", src_ip.c_str(), src_port, buffer);
            }

            //把数据显示到了服务器上，那么客户端不知道啊！所以我们要给客户端进行回数据。当当当当，介绍函数 sendto
            //ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
            // 跟我们的recvfrom简直一模一样，这里主要介绍最后两个参数
            //  第五个参数，我们想要给客户端回消息，那我们是不是要知道客户端的IP和端口号呢？我们怎么知道？是不是操作系统以及给我们把客户端端口号和IP
            //  设置到了我们的result缓冲区当中啊！并且len就是缓冲区result的大小！
            ssize_t ret = sendto(_sock,buffer, strlen(buffer), 0, (struct sockaddr*)&result,len);
            if(ret == -1)
            {
                logMessage(ERROR, "server sendto error %s", strerror(errno));
            }
        }
    }
private:
    uint16_t _port;  // 16位端口号
    std::string _ip; // 点分十进制表示IP,给用户看
    int _sock;
};
#endif